﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using Mono.Options;

using octalforty.Componento.Core.ApplicationServices;
using octalforty.Componento.Core.ObjectModel;
using octalforty.Componento.Impl;

namespace octalforty.Componento.Commands
{
    [Command("inspect", Aliases = "i", Description = "Inspects specified package")]
    public class InspectCommand : ICommand
    {
        public class Options
        {
            public bool Local
            { get; set; }

            public bool Latest
            { get; set; }

            public string RepositoryAlias
            { get; set; }

            public bool Probe
            { get; set; }
        }

        public MetadirectoryManagerService MetadirectoryManager
        { get; set; }

        public PackageRepositoryBrowserService PackageRepositoryBrowser
        { get; set; }

        public DependencyGraphManagerService DependencyGraphManager
        { get; set; }

        #region Implementation of ICommand
        public OptionSet GetOptionSet(object options)
        {
            var o = (Options)options;
            var optionSet = new OptionSet {
                { "p|probe", "Specifies whether to probe directories for \"lib\". Default is \"true\".", p => o.Probe = p != null },
                { "l|local", "", l => o.Local = l != null },
                { "r:|repository:", "", r => o.RepositoryAlias = r },
                { "la|latest", "", la => o.Latest = la != null }
            };

            return optionSet;
        }

        public void Execute(string[] args)
        {
            var options = new Options { Local = false, RepositoryAlias = "default", Probe = true, Latest = true };
            var rest = GetOptionSet(options).Parse(args);
            
            var repositoryUri = MetadirectoryManager.GetRepositoryUri(Directory.GetCurrentDirectory(), options.Probe, options.RepositoryAlias);
            
            if(options.Latest)
            {
                var package = PackageRepositoryBrowser.GetPackageManifest(repositoryUri, rest[0], null);
                InspectPackage(repositoryUri, package);
            } // if
            else
            {
                var packages = PackageRepositoryBrowser.GetPackageManifests(repositoryUri, rest[0], null);
                foreach(var package in packages)
                    InspectPackage(repositoryUri, PackageRepositoryBrowser.GetPackageManifest(repositoryUri, rest[0], new PackageSpecification(
                        new VersionRequirement(package.Version), null, null)));
            } // else
        }

        private void InspectPackage(Uri repositoryUri, PackageManifest package)
        {
            var dependencyGraph = DependencyGraphManager.BuildDepedencyGraph(package, repositoryUri);
            
            Console.Out.WriteLine(ConsoleColor.White, "  {0} {1}", package.Name, package.Version);
            Console.WriteLine("{0}", Layout(package.Description, "    ", Console.WindowWidth - 10));

            var packageNode = dependencyGraph.GetNodeFor(package.Name, package.Version);

            Console.WriteLine();
            Console.Out.WriteLine(ConsoleColor.White, "    Dependencies:");
            var visitor = new DependencyGraphVisitor((depth, node) => Console.WriteLine("{0}{1} {2}", new string(' ', depth * 2 + 6), node.Manifest.Name, node.Manifest.Version));
            visitor.Visit(packageNode);

            Console.WriteLine();
            Console.Out.WriteLine(ConsoleColor.White, "    Resources:");
            long total = 0;

            visitor = new DependencyGraphVisitor((depth, node) => {
                if(node.Manifest.Resources.Any())
                {
                    Console.WriteLine("      {0} ({1:N2} Kb)", node.Manifest.Resources[0].Uri, node.Manifest.Resources[0].ContentLength / 1024m);
                    total += node.Manifest.Resources[0].ContentLength;
                } // if
                else
                    Console.WriteLine("    (unknown)");
            });

            visitor.Visit(packageNode);

            Console.WriteLine();
            Console.Out.WriteLine(ConsoleColor.White, "      Total: {0:N2} Kb", total / 1024m);

            Console.WriteLine();
            Console.Out.WriteLine(ConsoleColor.White, "    Supported Platforms:");

            foreach(var platformVersion in package.SupportedPlatforms)
                Console.WriteLine("      {0} {1}", platformVersion.Platform, platformVersion.Version);

            Console.WriteLine();
        }
        #endregion

        public IEnumerable<PackageReference> GetPackageDependencies(string name, Options options, List<string> resolved)
        {
            var repositoryUri = MetadirectoryManager.GetRepositoryUri(Directory.GetCurrentDirectory(), options.Probe, options.RepositoryAlias);
            var package = PackageRepositoryBrowser.GetPackageManifest(repositoryUri, name, null);

            if(package == null || resolved.Contains(package.Name)) yield break;

            foreach(var dependency in package.Dependencies)
            {
                resolved.Add(dependency.Name);

                //yield return dependency;

                foreach(var subdependency in GetPackageDependencies(dependency.Name, options, resolved))
                {
                    resolved.Add(dependency.Name);
                    yield return subdependency;
                }
            } // foreach
        }

        private string Layout(string text, string prefix, int n)
        {
            var s = text.Split(' ');
            var r = new StringBuilder();
            var c = new StringBuilder(prefix);

            for(var j = 0; j < s.Length; ++j)
            {
                if(c.Length + s[j].Length > n)
                {
                    r.Append(c.ToString()).AppendLine();
                    c = new StringBuilder(prefix);
                } // if

                c.Append(s[j] + ' ');
            } // for

            r.Append(c.ToString());

            return r.ToString();
        }
    }
}
